#ifndef TB_MATH_FIXED30_H
#define TB_MATH_FIXED30_H

#include "prefix.h"
#include "int32.h"
#include "../libm/libm.h"

__tb_extern_c_enter__

// constant
#define TB_FIXED30_ONE (1 << 30)
#define TB_FIXED30_HALF (1 << 29)
#define TB_FIXED30_MAX (TB_MAXS32)
#define TB_FIXED30_MIN (-TB_FIXED30_MAX)
#define TB_FIXED30_NAN ((tb_int_t)0x80000000)
#define TB_FIXED30_INF (TB_MAXS32)
#define TB_FIXED30_SQRT2 (0x5a827999)

// conversion
#ifdef TB_CONFIG_TYPE_HAVE_FLOAT
#ifndef tb_fixed30_to_float
#define tb_fixed30_to_float(x) (((x)*0.00000000093132257f))
#endif
#ifndef tb_float_to_fixed30
#ifdef __tb_debug__
#define tb_float_to_fixed30(x) tb_float_to_fixed30_check(x)
#else
#define tb_float_to_fixed30(x) ((tb_fixed30_t)((x)*TB_FIXED30_ONE))
#endif
#endif
#endif

#ifdef __tb_debug__
#define tb_fixed16_to_fixed30(x) tb_fixed16_to_fixed30_check(x)
#else
#define tb_fixed16_to_fixed30(x) ((x) << 14)
#endif
#define tb_fixed30_to_fixed16(x) ((x) >> 14)

// abs
#define tb_fixed30_abs(x) tb_abs(x)

// avg
#define tb_fixed30_avg(x, y) (((x) + (y)) >> 1)

// mul
#ifndef tb_fixed30_mul
#if 1
#define tb_fixed30_mul(x, y) tb_fixed30_mul_int64(x, y)
#elif defined(TB_CONFIG_TYPE_HAVE_FLOAT)
#define tb_fixed30_mul(x, y) tb_fixed30_mul_float(x, y)
#else
#define tb_fixed30_mul(x, y) tb_fixed30_mul_int32(x, y)
#endif
#endif

// div
#ifndef tb_fixed30_div
#if 1
#define tb_fixed30_div(x, y) tb_fixed30_div_int64(x, y)
#elif defined(TB_CONFIG_TYPE_HAVE_FLOAT)
#define tb_fixed30_div(x, y) tb_fixed30_div_float(x, y)
#else
#define tb_fixed30_div(x, y) tb_int32_div(x, y, 30)
#endif
#endif

// sqre
#ifndef tb_fixed30_sqre
#if 1
#define tb_fixed30_sqre(x) tb_fixed30_sqre_int64(x)
#elif defined(TB_CONFIG_TYPE_HAVE_FLOAT)
#define tb_fixed30_sqre(x) tb_fixed30_sqre_float(x)
#else
#define tb_fixed30_sqre(x) tb_fixed30_sqre_int32(x)
#endif
#endif

// sqrt
#ifndef tb_fixed30_sqrt
#define tb_fixed30_sqrt(x) tb_fixed30_sqrt_int32(x)
#endif

/* //////////////////////////////////////////////////////////////////////////////////////
 * inlines
 */

#ifdef __tb_debug__
#ifdef TB_CONFIG_TYPE_HAVE_FLOAT
    static __tb_inline__ tb_fixed30_t
    tb_float_to_fixed30_check(tb_float_t x)
{
    // check overflow, [-2., 2.]
    tb_assert(x >= -2. && x <= 2.);
    return ((tb_fixed30_t)((x)*TB_FIXED30_ONE));
}
#endif
static __tb_inline__ tb_fixed30_t tb_fixed16_to_fixed30_check(tb_fixed16_t x)
{
    // check overflow, [-2, 2]
    tb_assert((x >> 16) >= -2 && (x >> 16) <= 2);
    return (x << 14);
}
#endif

static __tb_inline__ tb_fixed30_t tb_fixed30_mul_int64(
    tb_fixed30_t x, tb_fixed30_t y)
{
    return (tb_fixed30_t)((tb_hong_t)x * y >> 30);
}
static __tb_inline__ tb_fixed30_t tb_fixed30_div_int64(
    tb_fixed30_t x, tb_fixed30_t y)
{
    tb_assert(y);
    return (tb_fixed30_t)((((tb_hong_t)x) << 30) / y);
}
static __tb_inline__ tb_fixed30_t tb_fixed30_sqre_int64(tb_fixed30_t x)
{
    return (tb_fixed30_t)((tb_hong_t)x * x >> 30);
}

#ifdef TB_CONFIG_TYPE_HAVE_FLOAT
static __tb_inline__ tb_fixed30_t tb_fixed30_mul_float(
    tb_fixed30_t x, tb_fixed30_t y)
{
    tb_float_t f = tb_fixed30_to_float(x) * tb_fixed30_to_float(y);
    return tb_float_to_fixed30(f);
}
static __tb_inline__ tb_fixed30_t tb_fixed30_div_float(
    tb_fixed30_t x, tb_fixed30_t y)
{
    tb_assert(y);
    return tb_float_to_fixed30((tb_float_t)x / y);
}
static __tb_inline__ tb_fixed30_t tb_fixed30_sqre_float(tb_fixed30_t x)
{
    tb_float_t f = tb_fixed30_to_float(x);
    f *= f;
    return tb_float_to_fixed30(f);
}
#endif

static __tb_inline__ tb_fixed30_t tb_fixed30_mul_int32(
    tb_fixed30_t x, tb_fixed30_t y)
{
    // get sign
    tb_int32_t s = tb_int32_get_sign(x ^ y);
    x = tb_fixed30_abs(x);
    y = tb_fixed30_abs(y);

    tb_uint32_t xh = x >> 16;
    tb_uint32_t xl = x & 0xffff;
    tb_uint32_t yh = y >> 16;
    tb_uint32_t yl = y & 0xffff;

    tb_uint32_t xyh = xh * yh;
    tb_uint32_t xyl = xl * yl;
    tb_uint32_t xyhl = xh * yl + xl * yh;

    tb_uint32_t lo = xyl + (xyhl << 16);
    tb_uint32_t hi = xyh + (xyhl >> 16) + (lo < xyl);

    // check overflow
    tb_assert(!(hi >> 29));

    tb_uint32_t r = (hi << 2) + (lo >> 30);
    return tb_int32_set_sign(r, s);
}

static __tb_inline__ tb_fixed30_t tb_fixed30_sqre_int32(tb_fixed30_t x)
{
    x = tb_fixed30_abs(x);

    tb_uint32_t xh = x >> 16;
    tb_uint32_t xl = x & 0xffff;

    tb_uint32_t xxh = xh * xh;
    tb_uint32_t xxl = xl * xl;
    tb_uint32_t xxhl = (xh * xl) << 1;

    tb_uint32_t lo = xxl + (xxhl << 16);
    tb_uint32_t hi = xxh + (xxhl >> 16) + (lo < xxl);

    // check overflow
    tb_assert(!(hi >> 29));

    return ((hi << 2) + (lo >> 30));
}
static __tb_inline__ tb_fixed30_t tb_fixed30_sqrt_int32(tb_fixed30_t x)
{
    tb_assert(x > 0);
    return (x > 0 ? (tb_isqrti(x) << 15) : 0);
}

__tb_extern_c_leave__

#endif // !TB_MATH_FIXED30_H
